/*
 * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
 * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice including the dates of first publication and
 * either this permission notice or a reference to
 * http://oss.sgi.com/projects/FreeB/
 * shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of Silicon Graphics, Inc.
 * shall not be used in advertising or otherwise to promote the sale, use or
 * other dealings in this Software without prior written authorization from
 * Silicon Graphics, Inc.
 */
/*
 ** Author: Eric Veach, July 1994.
 **
 */

#include "gluos.h"
#include <stddef.h>
#include <assert.h>
#include <setjmp.h>
#include "memalloc.h"
#include "tess.h"
#include "mesh.h"
#include "normal.h"
#include "sweep.h"
#include "tessmono.h"
#include "render.h"

#define GLU_TESS_DEFAULT_TOLERANCE 0.0
#define GLU_TESS_MESH		100112	/* void (*)(GLUmesh *mesh)	    */

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/*ARGSUSED*/static void GLAPIENTRY noBegin(GLenum type) {
}
/*ARGSUSED*/static void GLAPIENTRY noEdgeFlag(GLboolean boundaryEdge) {
}
/*ARGSUSED*/static void GLAPIENTRY noVertex(void *data) {
}
/*ARGSUSED*/static void GLAPIENTRY noEnd(void) {
}
/*ARGSUSED*/static void GLAPIENTRY noError(GLenum errnum) {
}
/*ARGSUSED*/static void GLAPIENTRY noCombine(GLdouble coords[3], void *data[4],
      GLfloat weight[4], void **dataOut) {
}
/*ARGSUSED*/static void GLAPIENTRY noMesh(GLUmesh *mesh) {
}

/*ARGSUSED*/void GLAPIENTRY __gl_noBeginData(GLenum type,
      void *polygonData) {
}
/*ARGSUSED*/void GLAPIENTRY __gl_noEdgeFlagData(GLboolean boundaryEdge,
      void *polygonData) {
}
/*ARGSUSED*/void GLAPIENTRY __gl_noVertexData(void *data,
      void *polygonData) {
}
/*ARGSUSED*/void GLAPIENTRY __gl_noEndData(void *polygonData) {
}
/*ARGSUSED*/void GLAPIENTRY __gl_noErrorData(GLenum errnum,
      void *polygonData) {
}
/*ARGSUSED*/void GLAPIENTRY __gl_noCombineData(GLdouble coords[3],
      void *data[4],
      GLfloat weight[4],
      void **outData,
      void *polygonData) {
}

/* Half-edges are allocated in pairs (see mesh.c) */
typedef struct {
   GLUhalfEdge e, eSym;
} EdgePair;

#undef	MAX
#define MAX(a,b)	((a) > (b) ? (a) : (b))
#define MAX_FAST_ALLOC	(MAX(sizeof(EdgePair), \
                         MAX(sizeof(GLUvertex),sizeof(GLUface))))

GLUtesselator * GLAPIENTRY
gluNewTess(void)
{
   GLUtesselator *tess;

   /* Only initialize fields which can be changed by the api.  Other fields
    * are initialized where they are used.
    */

   if (memInit(MAX_FAST_ALLOC) == 0) {
      return 0; /* out of memory */
   }
   tess = (GLUtesselator *) memAlloc(sizeof(GLUtesselator));
   if (tess == NULL) {
      return 0; /* out of memory */
   }

   tess->state = T_DORMANT;

   tess->normal[0] = 0;
   tess->normal[1] = 0;
   tess->normal[2] = 0;

   tess->relTolerance = GLU_TESS_DEFAULT_TOLERANCE;
   tess->windingRule = GLU_TESS_WINDING_ODD;
   tess->flagBoundary = FALSE;
   tess->boundaryOnly = FALSE;

   tess->callBegin = &noBegin;
   tess->callEdgeFlag = &noEdgeFlag;
   tess->callVertex = &noVertex;
   tess->callEnd = &noEnd;

   tess->callError = &noError;
   tess->callCombine = &noCombine;
   tess->callMesh = &noMesh;

   tess->callBeginData = &__gl_noBeginData;
   tess->callEdgeFlagData = &__gl_noEdgeFlagData;
   tess->callVertexData = &__gl_noVertexData;
   tess->callEndData = &__gl_noEndData;
   tess->callErrorData = &__gl_noErrorData;
   tess->callCombineData = &__gl_noCombineData;

   tess->polygonData = NULL;

   return tess;
}

static void MakeDormant(GLUtesselator *tess)
{
   /* Return the tessellator to its original dormant state. */

   if (tess->mesh != NULL) {
      __gl_meshDeleteMesh(tess->mesh);
   }
   tess->state = T_DORMANT;
   tess->lastEdge = NULL;
   tess->mesh = NULL;
}

#define RequireState( tess, s )   if( tess->state != s ) GotoState(tess,s)

static void GotoState(GLUtesselator *tess, enum TessState newState)
{
   while (tess->state != newState) {
      /* We change the current state one level at a time, to get to
       * the desired state.
       */
      if (tess->state < newState) {
         switch (tess->state) {
         case T_DORMANT:
            CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_POLYGON);
            gluTessBeginPolygon(tess, NULL);
            break;
         case T_IN_POLYGON:
            CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_BEGIN_CONTOUR);
            gluTessBeginContour(tess);
            break;
         default:
            ;
         }
      }
      else {
         switch (tess->state) {
         case T_IN_CONTOUR:
            CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_CONTOUR);
            gluTessEndContour(tess);
            break;
         case T_IN_POLYGON:
            CALL_ERROR_OR_ERROR_DATA( GLU_TESS_MISSING_END_POLYGON);
            /* gluTessEndPolygon( tess ) is too much work! */
            MakeDormant(tess);
            break;
         default:
            ;
         }
      }
   }
}

void GLAPIENTRY
gluDeleteTess(GLUtesselator *tess)
{
   RequireState( tess, T_DORMANT);
   memFree(tess);
}

void GLAPIENTRY
gluTessProperty(GLUtesselator *tess, GLenum which, GLdouble value)
{
   GLenum windingRule;

   switch (which) {
   case GLU_TESS_TOLERANCE:
      if (value < 0.0 || value > 1.0)
         break;
      tess->relTolerance = value;
      return;

   case GLU_TESS_WINDING_RULE:
      windingRule = (GLenum) value;
      if (windingRule != value)
         break; /* not an integer */

      switch (windingRule) {
      case GLU_TESS_WINDING_ODD:
         case GLU_TESS_WINDING_NONZERO:
         case GLU_TESS_WINDING_POSITIVE:
         case GLU_TESS_WINDING_NEGATIVE:
         case GLU_TESS_WINDING_ABS_GEQ_TWO:
         tess->windingRule = windingRule;
         return;
      default:
         break;
      }

   case GLU_TESS_BOUNDARY_ONLY:
      tess->boundaryOnly = (value != 0);
      return;

   default:
      CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM);
      return;
   }
   CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_VALUE);
}

/* Returns tessellator property */
void GLAPIENTRY
gluGetTessProperty(GLUtesselator *tess, GLenum which, GLdouble *value)
{
   switch (which) {
   case GLU_TESS_TOLERANCE:
      /* tolerance should be in range [0..1] */
      assert(0.0 <= tess->relTolerance && tess->relTolerance <= 1.0);
      *value = tess->relTolerance;
      break;
   case GLU_TESS_WINDING_RULE:
      assert(tess->windingRule == GLU_TESS_WINDING_ODD ||
            tess->windingRule == GLU_TESS_WINDING_NONZERO ||
            tess->windingRule == GLU_TESS_WINDING_POSITIVE ||
            tess->windingRule == GLU_TESS_WINDING_NEGATIVE ||
            tess->windingRule == GLU_TESS_WINDING_ABS_GEQ_TWO);
      *value = tess->windingRule;
      break;
   case GLU_TESS_BOUNDARY_ONLY:
      assert(tess->boundaryOnly == TRUE || tess->boundaryOnly == FALSE);
      *value = tess->boundaryOnly;
      break;
   default:
      *value = 0.0;
      CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM);
      break;
   }
} /* gluGetTessProperty() */

void GLAPIENTRY
gluTessNormal(GLUtesselator *tess, GLdouble x, GLdouble y, GLdouble z)
{
   tess->normal[0] = x;
   tess->normal[1] = y;
   tess->normal[2] = z;
}

void GLAPIENTRY
gluTessCallback(GLUtesselator *tess, GLenum which, _GLUfuncptr fn)
{
   switch (which) {
   case GLU_TESS_BEGIN:
      tess->callBegin = (fn == NULL) ? &noBegin : (void (GLAPIENTRY *)(GLenum)) fn;
      return;
   case GLU_TESS_BEGIN_DATA:
      tess->callBeginData = (fn == NULL) ?
                                           &__gl_noBeginData :
                                           (void (GLAPIENTRY *)(GLenum, void *)) fn;
      return;
   case GLU_TESS_EDGE_FLAG:
      tess->callEdgeFlag = (fn == NULL) ? &noEdgeFlag :
                                          (void (GLAPIENTRY *)(GLboolean)) fn;
      /* If the client wants boundary edges to be flagged,
       * we render everything as separate triangles (no strips or fans).
       */
      tess->flagBoundary = (fn != NULL);
      return;
   case GLU_TESS_EDGE_FLAG_DATA:
      tess->callEdgeFlagData = (fn == NULL) ?
                                              &__gl_noEdgeFlagData :
                                              (void (GLAPIENTRY *)(GLboolean, void *)) fn;
      /* If the client wants boundary edges to be flagged,
       * we render everything as separate triangles (no strips or fans).
       */
      tess->flagBoundary = (fn != NULL);
      return;
   case GLU_TESS_VERTEX:
      tess->callVertex = (fn == NULL) ? &noVertex :
                                        (void (GLAPIENTRY *)(void *)) fn;
      return;
   case GLU_TESS_VERTEX_DATA:
      tess->callVertexData = (fn == NULL) ?
                                            &__gl_noVertexData :
                                            (void (GLAPIENTRY *)(void *, void *)) fn;
      return;
   case GLU_TESS_END:
      tess->callEnd = (fn == NULL) ? &noEnd : (void (GLAPIENTRY *)(void)) fn;
      return;
   case GLU_TESS_END_DATA:
      tess->callEndData = (fn == NULL) ? &__gl_noEndData :
                                         (void (GLAPIENTRY *)(void *)) fn;
      return;
   case GLU_TESS_ERROR:
      tess->callError = (fn == NULL) ? &noError : (void (GLAPIENTRY *)(GLenum)) fn;
      return;
   case GLU_TESS_ERROR_DATA:
      tess->callErrorData = (fn == NULL) ?
                                           &__gl_noErrorData :
                                           (void (GLAPIENTRY *)(GLenum, void *)) fn;
      return;
   case GLU_TESS_COMBINE:
      tess->callCombine =
            (fn == NULL) ? &noCombine :
                           (void (GLAPIENTRY *)(GLdouble[3], void *[4], GLfloat[4], void **)) fn;
      return;
   case GLU_TESS_COMBINE_DATA:
      tess->callCombineData = (fn == NULL) ? &__gl_noCombineData :
                                             (void (GLAPIENTRY *)(GLdouble[3],
                                                   void *[4],
                                                   GLfloat[4],
                                                   void **,
                                                   void *)) fn;
      return;
   case GLU_TESS_MESH:
      tess->callMesh = (fn == NULL) ? &noMesh : (void (GLAPIENTRY *)(GLUmesh *)) fn;
      return;
   default:
      CALL_ERROR_OR_ERROR_DATA( GLU_INVALID_ENUM);
      return;
   }
}

static int AddVertex(GLUtesselator *tess, GLdouble coords[3], void *data)
{
   GLUhalfEdge *e;

   e = tess->lastEdge;
   if (e == NULL) {
      /* Make a self-loop (one vertex, one edge). */

      e = __gl_meshMakeEdge(tess->mesh);
      if (e == NULL)
         return 0;
      if (!__gl_meshSplice(e, e->Sym))
         return 0;
   }
   else {
      /* Create a new vertex and edge which immediately follow e
       * in the ordering around the left face.
       */
      if (__gl_meshSplitEdge(e) == NULL)
         return 0;
      e = e->Lnext;
   }

   /* The new vertex is now e->Org. */
   e->Org->data = data;
   e->Org->coords[0] = coords[0];
   e->Org->coords[1] = coords[1];
   e->Org->coords[2] = coords[2];

   /* The winding of an edge says how the winding number changes as we
    * cross from the edge''s right face to its left face.  We add the
    * vertices in such an order that a CCW contour will add +1 to
    * the winding number of the region inside the contour.
    */
   e->winding = 1;
   e->Sym->winding = -1;

   tess->lastEdge = e;

   return 1;
}

static void CacheVertex(GLUtesselator *tess, GLdouble coords[3], void *data)
{
   CachedVertex *v = &tess->cache[tess->cacheCount];

   v->data = data;
   v->coords[0] = coords[0];
   v->coords[1] = coords[1];
   v->coords[2] = coords[2];
   ++tess->cacheCount;
}

static int EmptyCache(GLUtesselator *tess)
{
   CachedVertex *v = tess->cache;
   CachedVertex *vLast;

   tess->mesh = __gl_meshNewMesh();
   if (tess->mesh == NULL)
      return 0;

   for (vLast = v + tess->cacheCount; v < vLast; ++v) {
      if (!AddVertex(tess, v->coords, v->data))
         return 0;
   }
   tess->cacheCount = 0;
   tess->emptyCache = FALSE;

   return 1;
}

void GLAPIENTRY
gluTessVertex(GLUtesselator *tess, GLdouble coords[3], void *data)
{
   int i, tooLarge = FALSE;
   GLdouble x, clamped[3];

   RequireState( tess, T_IN_CONTOUR);

   if (tess->emptyCache) {
      if (!EmptyCache(tess)) {
         CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY);
         return;
      }
      tess->lastEdge = NULL;
   }
   for (i = 0; i < 3; ++i) {
      x = coords[i];
      if (x < -GLU_TESS_MAX_COORD) {
         x = -GLU_TESS_MAX_COORD;
         tooLarge = TRUE;
      }
      if (x > GLU_TESS_MAX_COORD) {
         x = GLU_TESS_MAX_COORD;
         tooLarge = TRUE;
      }
      clamped[i] = x;
   }
   if (tooLarge) {
      CALL_ERROR_OR_ERROR_DATA( GLU_TESS_COORD_TOO_LARGE);
   }

   if (tess->mesh == NULL) {
      if (tess->cacheCount < TESS_MAX_CACHE) {
         CacheVertex(tess, clamped, data);
         return;
      }
      if (!EmptyCache(tess)) {
         CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY);
         return;
      }
   }
   if (!AddVertex(tess, clamped, data)) {
      CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY);
   }
}

void GLAPIENTRY
gluTessBeginPolygon(GLUtesselator *tess, void *data)
{
   RequireState( tess, T_DORMANT);

   tess->state = T_IN_POLYGON;
   tess->cacheCount = 0;
   tess->emptyCache = FALSE;
   tess->mesh = NULL;

   tess->polygonData = data;
}

void GLAPIENTRY
gluTessBeginContour(GLUtesselator *tess)
{
   RequireState( tess, T_IN_POLYGON);

   tess->state = T_IN_CONTOUR;
   tess->lastEdge = NULL;
   if (tess->cacheCount > 0) {
      /* Just set a flag so we don't get confused by empty contours
       * -- these can be generated accidentally with the obsolete
       * NextContour() interface.
       */
      tess->emptyCache = TRUE;
   }
}

void GLAPIENTRY
gluTessEndContour(GLUtesselator *tess)
{
   RequireState( tess, T_IN_CONTOUR);
   tess->state = T_IN_POLYGON;
}

void GLAPIENTRY
gluTessEndPolygon(GLUtesselator *tess)
{
   GLUmesh *mesh;

   if (setjmp(tess->env) != 0) {
      /* come back here if out of memory */
      CALL_ERROR_OR_ERROR_DATA( GLU_OUT_OF_MEMORY);
      return;
   }

   RequireState( tess, T_IN_POLYGON);
   tess->state = T_DORMANT;

   if (tess->mesh == NULL) {
      if (!tess->flagBoundary && tess->callMesh == &noMesh) {

         /* Try some special code to make the easy cases go quickly
          * (eg. convex polygons).  This code does NOT handle multiple contours,
          * intersections, edge flags, and of course it does not generate
          * an explicit mesh either.
          */
         if (__gl_renderCache(tess)) {
            tess->polygonData = NULL;
            return;
         }
      }
      if (!EmptyCache(tess))
         longjmp(tess->env, 1); /* could've used a label*/
   }

   /* Determine the polygon normal and project vertices onto the plane
    * of the polygon.
    */
   __gl_projectPolygon(tess);

   /* __gl_computeInterior( tess ) computes the planar arrangement specified
    * by the given contours, and further subdivides this arrangement
    * into regions.  Each region is marked "inside" if it belongs
    * to the polygon, according to the rule given by tess->windingRule.
    * Each interior region is guaranteed be monotone.
    */
   if (!__gl_computeInterior(tess)) {
      longjmp(tess->env, 1); /* could've used a label */
   }

   mesh = tess->mesh;
   if (!tess->fatalError) {
      int rc = 1;

      /* If the user wants only the boundary contours, we throw away all edges
       * except those which separate the interior from the exterior.
       * Otherwise we tessellate all the regions marked "inside".
       */
      if (tess->boundaryOnly) {
         rc = __gl_meshSetWindingNumber(mesh, 1, TRUE);
      }
      else {
         rc = __gl_meshTessellateInterior(mesh);
      }
      if (rc == 0)
         longjmp(tess->env, 1); /* could've used a label */

      __gl_meshCheckMesh(mesh);

      if (tess->callBegin != &noBegin || tess->callEnd != &noEnd
            || tess->callVertex != &noVertex || tess->callEdgeFlag != &noEdgeFlag
            || tess->callBeginData != &__gl_noBeginData
            || tess->callEndData != &__gl_noEndData
            || tess->callVertexData != &__gl_noVertexData
            || tess->callEdgeFlagData != &__gl_noEdgeFlagData)
                  {
         if (tess->boundaryOnly) {
            __gl_renderBoundary(tess, mesh); /* output boundary contours */
         }
         else {
            __gl_renderMesh(tess, mesh); /* output strips and fans */
         }
      }
      if (tess->callMesh != &noMesh) {

         /* Throw away the exterior faces, so that all faces are interior.
          * This way the user doesn't have to check the "inside" flag,
          * and we don't need to even reveal its existence.  It also leaves
          * the freedom for an implementation to not generate the exterior
          * faces in the first place.
          */
         __gl_meshDiscardExterior(mesh);
         (*tess->callMesh)(mesh); /* user wants the mesh itself */
         tess->mesh = NULL;
         tess->polygonData = NULL;
         return;
      }
   }
   __gl_meshDeleteMesh(mesh);
   tess->polygonData = NULL;
   tess->mesh = NULL;
}

/*XXXblythe unused function*/
#if 0
void GLAPIENTRY
gluDeleteMesh( GLUmesh *mesh )
{
   __gl_meshDeleteMesh( mesh );
}
#endif

/*******************************************************/

/* Obsolete calls -- for backward compatibility */

void GLAPIENTRY
gluBeginPolygon(GLUtesselator *tess)
{
   gluTessBeginPolygon(tess, NULL);
   gluTessBeginContour(tess);
}

/*ARGSUSED*/
void GLAPIENTRY
gluNextContour(GLUtesselator *tess, GLenum type)
{
   gluTessEndContour(tess);
   gluTessBeginContour(tess);
}

void GLAPIENTRY
gluEndPolygon(GLUtesselator *tess)
{
   gluTessEndContour(tess);
   gluTessEndPolygon(tess);
}
